﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Pout_Porri.Utils;
using Pout_Porri.Levels;

namespace Pout_Porri.Objects
{
    abstract class PhysicsGameEntity : MovableGameEntity
    {
        /// <summary>
        /// the mass, between 0 and 1
        /// </summary>
        private float mass;
        public float Mass
        {
            get { return mass; }
            set
            {
                if (value > 1)
                {
                    value = 1;
                }
                else if (value < 0)
                {
                    value = 0;
                }
                mass = value;
            }
        }

        public int x, y, cima, baixo, esquerda, direita;

        private Vector2 gravityDirection;

        /// <summary>
        /// If the player is Running
        /// </summary>
        protected Boolean isRunning = false;

        /// <summary>
        /// if the player is climbing is up
        /// </summary>
        protected Boolean isClimbingUp = false;

        /// <summary>
        /// if the player is climbing is down
        /// </summary>
        protected Boolean isClimbingDown = false;

        /// <summary>
        /// If the player is falling into the hole
        /// </summary>
        protected Boolean isFallingHole = false;

        /// <summary>
        /// Save the current Animation Rectangle
        /// for pixel collision use
        /// </summary>
        public Rectangle currentAnimationRect;

        public Boolean disablePixelCollision = false;

        public Boolean isOnEntity = false;
        
        /// <summary>
        /// If the player is grounded
        /// </summary>
        private bool isGrounded = false;

        public PhysicsGameEntity(Texture2D image, Vector2 position, Vector2 velocity, bool isAnimated)
            : base(image, position, velocity, isAnimated)
        {
            this.Velocity = new Vector2(velocity.X, Physics.GRAVITY);
            gravityDirection = new Vector2(0, 1);
            this.direction.Y = gravityDirection.Y;
        }

        /// <summary>
        /// Check collision between two physics object
        /// </summary>
        /// <param name="pGameEntity">An physics game entity</param>
        /// <returns>True if collide</returns>
        public bool isCollide(PhysicsGameEntity pGameEntity)
        {
            return this.CollisionRect.Intersects(pGameEntity.CollisionRect);
        }

        public bool ColisaoPorPixel(PhysicsGameEntity j1, PhysicsGameEntity j2)
        {
            Color[] colorData1 = new Color[j1.spriteTexture.Bounds.Width * j1.spriteTexture.Bounds.Height];

            Color[] colorData2 = new Color[j2.spriteTexture.Bounds.Width * j2.spriteTexture.Bounds.Height];

            j1.texture.GetData(0, j1.AnimationRect(j1.getCurrentAnimation()), colorData1, 0,
                (int)j1.getCurrentAnimation().getFrameSize().X * (int)j1.getCurrentAnimation().getFrameSize().Y);

            j2.texture.GetData(0, j2.AnimationRect(j2.getCurrentAnimation()), colorData2, 0,
                (int)j2.getCurrentAnimation().getFrameSize().X * (int)j2.getCurrentAnimation().getFrameSize().Y);

            // Encontra os limites do retangulo de interseção
            cima = Math.Max(j1.CollisionRect.Top, j2.CollisionRect.Top);

            baixo = Math.Min(j1.CollisionRect.Bottom, j2.CollisionRect.Bottom);

            esquerda = Math.Max(j1.CollisionRect.Left, j2.CollisionRect.Left);

            direita = Math.Min(j1.CollisionRect.Right, j2.CollisionRect.Right);

            // Verifica todos os pontos dentro do limite de intereseção 
            //TODO verificar se os retangulos estao um ao lado do outro
            for (y = cima; y < baixo; y++)
            {
                for (x = esquerda; x < direita; x++)
                {
                    // Verifica a cor de ambos os pixels neste momento
                    Color color1 = colorData1[(x - j1.CollisionRect.Left) +
                    (y - j1.CollisionRect.Top) * j1.CollisionRect.Width];
                    Color color2 = colorData2[(x - j2.CollisionRect.Left) +
                    (y - j2.CollisionRect.Top) * j2.CollisionRect.Width];

                    // Se ambos os píxels não são completamente diferentes
                    if (color1.A != 0 && color2.A != 0)
                    {
                        //Um cruzamento de pixel foi encontrado
                        //Game1.window.Title = "  " + "alpha";
                        return true;
                    }
                    //else
                       // Game1.window.Title = "";
                }
            }
            // Não foi encontrado cruzamento entre os pixels
            return false;
        }

        /// <summary>
        /// if is over a platform
        /// </summary>
        /// <returns>true if it is</returns>
        public bool IsGrounded()
        {
            return isGrounded;
        }

        public virtual void setGrounded(bool b)
        {
            if (b == true)
            {
                velocity.Y = 0;
                direction.Y = 0;
            }
            else
            {
                direction.Y = 1;
            }
            this.isGrounded = b;
        }

        /// <summary>
        /// Apply gravity in this object
        /// </summary>
        public void applyGravity(GameTime gameTime)
        {
            if (this.mass > 0)
            {
                velocity.Y += Physics.GRAVITY;
                position.Y += velocity.Y;

                if ((!isClimbingDown && !isClimbingUp) || isFallingHole)
                {
                    if (velocity.Y < 0)
                    {
                        direction.Y = -1;
                    }
                    if (velocity.Y > 0)
                    {
                        direction.Y = 1;
                    }
                }
            }
        }

        /// <summary>
        /// Apply movemment in this object
        /// </summary>
        /// <param name="gameTime">the GameTime</param>
        protected virtual void applyMovement(GameTime gameTime)
        {
            if (isRunning && this.Direction.X != 0)
                this.velocity.X = (velocity.X >= MaxVelocityX ? MaxVelocityX : velocity.X += AccelerationX);    
            else
                this.velocity.X = DefaultVelocityX;

            if (!isClimbingDown && !isClimbingUp)
                position.X += Velocity.X * direction.X;

            if (isFallingHole)
            {
                applyGravity(gameTime);
            }
            else if (isClimbingUp || isClimbingDown )
            {
                //do nothing
            }
            else
            {
               checkMatrixCollisionInX();
               applyGravity(gameTime);
            }
            
            // TODO: remover depois de testado
            //if (!isClimbingDown && !isClimbingUp) 
            //    position.X += Velocity.X * direction.X;
        }

        /// <summary>
        /// Ajusta a posição se colidiu verticalmente com um tile
        /// </summary>
        protected void checkMatrixCollisionInY()
        {
            Point tile01 = new Point();
            Point tile02 = new Point();

            Rectangle rectTestCollision = CollisionRect;

            rectTestCollision.Width--;
            rectTestCollision.Height--;

            if (direction.Y < 0)
            {
                tile01 = new Point(rectTestCollision.Left / GameConfig.TILE_SIZE, rectTestCollision.Top / GameConfig.TILE_SIZE);
                tile02 = new Point(rectTestCollision.Right / GameConfig.TILE_SIZE, rectTestCollision.Top / GameConfig.TILE_SIZE);
            }
            else if (direction.Y > 0)
            {
                tile01 = new Point(rectTestCollision.Left / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
                tile02 = new Point(rectTestCollision.Right / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
            }

            // verificando os tiles inclinados
            Point tileCenter = new Point(rectTestCollision.Center.X / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
            TileType tileTypeCenter = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[tileCenter.Y, tileCenter.X]);

            if (tileTypeCenter == TileType.SLOPED)
            {
                int id = MyLevel.CollisionMatrix[tileCenter.Y, tileCenter.X];
                int? yTile = MyLevel.Tileset.getYValueForSlopedTile(id, tileCenter, rectTestCollision);
                if (yTile != null)
                {
                    if (rectTestCollision.Bottom >= yTile)
                    {
                        this.position.Y = yTile.Value - rectTestCollision.Height - 1;
                        setGrounded(true);
                        return;
                    }
                }
            }
            else
            {
                // verificando colisão com os tiles das pontas do sprite
                // e com os tiles que estiverem pegando no meio do sprite
                TileType tileType;
                for (int x = tile01.X; x < (tile02.X + 1); x++)
                {
                    tileType = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[tile01.Y, x]);

                    if (tileType == TileType.PLATAFORM)
                    {
                        if (direction.Y < 0)
                            rectTestCollision.Y = (tile01.Y + 1) * GameConfig.TILE_SIZE;
                        else
                        {
                            rectTestCollision.Y = (tile01.Y * GameConfig.TILE_SIZE) - (rectTestCollision.Height + 1);
                            setGrounded(true);
                        }

                        this.position.Y = rectTestCollision.Y;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Verifica se o Mario caiu no buraco
        /// </summary>
        protected bool checkHoleCollision()
        {
            Rectangle rectTestCollision = CollisionRect;
            Point tileCenter = new Point(rectTestCollision.Center.X / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);

            if (tileCenter.X == 9 && tileCenter.Y == 10 && !isClimbingUp)
            {
                int id = MyLevel.CollisionMatrix[tileCenter.X, tileCenter.Y];

                isFallingHole = true;
                // Está na escada
                return true;
            }
            else if ((tileCenter.X == 7 || tileCenter.X == 6 || tileCenter.X == 11 ||
                      tileCenter.X == 8 || tileCenter.X == 9 || tileCenter.X == 10) &&
                      tileCenter.Y == 15)
            {
                isFallingHole = false;
                this.setGrounded(true);
                return false;
            }
            return false;
        }

        protected bool checkLadderCollision()
        {
            //return false;
            Rectangle rectTestCollision = CollisionRect;
            Point tileCenter = new Point(rectTestCollision.Center.X / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
            if ((tileCenter.X == 9 || tileCenter.X == 9 || tileCenter.X == 9) &&
                (tileCenter.Y == 15 || tileCenter.Y == 14 || tileCenter.Y == 13 ||
                tileCenter.Y == 12 || tileCenter.Y == 11 || tileCenter.Y == 10) && !isFallingHole)
            {
                int id = MyLevel.CollisionMatrix[tileCenter.X, tileCenter.Y];

                //if (Game1.keyboardState.IsKeyDown(Keys.Up) || Game1.keyboardState.IsKeyDown(Keys.Down))
                //{
                isClimbingUp = true;

                if (tileCenter.Y == 15)
                    isClimbingDown = false;
                else
                    isClimbingDown = true;
                //}
                return true;
            }
            else
            {
                isClimbingUp = false;
                isClimbingDown = false;
                setGrounded(false);
                return false;
            }
        }

        /// <summary>
        /// Ajusta a posição se colidiu horizontalmente com um tile
        /// </summary>
        protected void checkMatrixCollisionInX()
        {
            Point tile01 = new Point();
            Point tile02 = new Point();

            Rectangle rectTestCollision = CollisionRect;

            rectTestCollision.Width--;
            rectTestCollision.Height--;

            if (direction.X != 0)
            {
                if (direction.X < 0)
                {
                    tile01 = new Point(rectTestCollision.Left / GameConfig.TILE_SIZE, rectTestCollision.Top / GameConfig.TILE_SIZE);
                    tile02 = new Point(rectTestCollision.Left / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
                }
                else if (direction.X > 0)
                {
                    tile01 = new Point(rectTestCollision.Right / GameConfig.TILE_SIZE, rectTestCollision.Top / GameConfig.TILE_SIZE);
                    tile02 = new Point(rectTestCollision.Right / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
                }

                // verificando os tiles inclinados
                Point tileCenter = new Point(rectTestCollision.Center.X / GameConfig.TILE_SIZE, rectTestCollision.Bottom / GameConfig.TILE_SIZE);
                TileType tileTypeCenter = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[tileCenter.Y, tileCenter.X]);
                if (tileTypeCenter == TileType.SLOPED)
                {
                    int id = MyLevel.CollisionMatrix[tileCenter.Y, tileCenter.X];
                    int? yTile = MyLevel.Tileset.getYValueForSlopedTile(id, tileCenter, rectTestCollision);
                    if (yTile != null)
                    {
                        if (rectTestCollision.Bottom >= yTile)
                        {
                            this.position.Y = yTile.Value - rectTestCollision.Height - 1;
                            setGrounded(true);
                            return;
                        }
                    }
                }
                else
                {
                    // verificando colisão com os tiles das pontas do sprite
                    // e com os tiles que estiverem pegando no meio do sprite
                    TileType tileType;
                    TileType tileTypeBack;
                    for (int y = tile01.Y; y < (tile02.Y + 1); y++)
                    {
                        tileType = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[y, tile01.X]);
                        if (direction.X < 0)
                            tileTypeBack = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[y, tile01.X + 1]);
                        else
                            tileTypeBack = MyLevel.Tileset.getTileType(MyLevel.CollisionMatrix[y, tile01.X - 1]);

                        if (tileType == TileType.PLATAFORM && tileTypeBack != TileType.SLOPED)
                        {
                            if (direction.X < 0)
                                rectTestCollision.X = (tile01.X + 1) * GameConfig.TILE_SIZE;
                            else
                                rectTestCollision.X = (tile01.X * GameConfig.TILE_SIZE) - (rectTestCollision.Width + 1);

                            this.position.X = rectTestCollision.X;
                            break;
                        }
                    }
                }
            }
        }
    }
}
